Skip to content

03 磁盘好坏它说了算 - 磁盘与 FIO

磁盘的分类

有多种磁盘分类标准,按磁盘存储实现技术分类,可划分为:

  • 机械硬盘:
    • 使用旋转的磁盘和移动的读写头来存储和检索数据;
    • 其优点在于存储容量大、价格相对较低;
    • 其劣势在于容易受到物理冲击和振动的影响,寿命有限;
  • 固态硬盘:
    • 使用闪存芯片来存储数据;
    • 相对机械硬盘具有更快的读写速度,并且更可靠、更耐用;
    • 价格相对机械硬盘更高;

SATA 和 NVMe

SSD 有两种常用的接口形式,分别是 SATA(Serial Advanced Technology Attachment) 和 NVMe(Non-Volatile Memory Express),两者主要区别包括:

  • 传输速度:SATA 接口传输速度最大约 600MB/s,NVMe 可达数 GB/s;
  • 读写延时:NVMe 接口的读写延时远低于 SATA 接口;
  • 习惯上,SSD 一般指的是 SATA SSD,一般称 NVMe SSD 为 NVMe

磁盘的评价体系

核心评价指标:

  • 容量、尺寸、接口等静态指标;
  • IOPS - Input/Output Operations Per Second,每秒读写次数,是随机读写的评价指标;
  • BW - BandWidth,读写带宽,一般用来衡量磁盘顺序读写的性能;
  • Latency - 读写延时,指发送 I/O 请求和接收响应之间的间隔时间;

fio BenchMark

  • fio(Flexible I/O Tester)是一个开源的 I/O 压力测试和基准测试工具,它可以用来测试存储设备(如硬盘、SSD、网络存储等)的性能,特别是存储设备如硬盘、固态硬盘(SSD)、存储阵列等。
bash
# 安装 fio 工具
 yum install fio -y

# 在 /dev/sdb1 设备上执行一个 20 秒的随机读取测试,使用异步 I/O 引擎进行 4KB 大小的读取操作,测试时不使用操作系统的缓存,并且使用 64 个并发线程,每个线程同时处理 64 个 I/O 请求。测试完成后,将输出整体的性能报告。
# fio -filename=/dev/sdb1 -iodepth=64 -ioengine=libaio -direct=1 -rw=randread -bs=4 numjobs=64 -runtime=20 -group_reporting -name=test-rand-read
参数说明
-filename测试设备,可为文件系统(如 /dev/sdb1)、裸设备(如 /dev/sdb)和文件(/home/test.bin
-iodepth测试时的 IO 队列深度,在测试延时的时候,该值一般为 1;测试 IOPS 和 BW 时,该值一般较大
-ioengine测试所用的 I/O 引擎,通常为 libaio,还可能为 psync
-direct1 表明绕过机器自带的 buffer,测试结果更真实
-rw一般为顺序读/写/读写(read/write/readwrite)、随机读/写/读写(randread/randwrite/randrw
-bs单次 IO 测试的块文件大小,测试 Latency 和 IOPS 时,该值一般为 4k;测试 BW 时,该值一般为 128k
-numjobs测试过程 fio 线程数
-runtime测试时间
-group_reporting启用分组报告模式。fio 将汇总所有 job 的结果,并以一个整体的方式输出统计信息。
-name本次测试的名称

注意

写测试会覆盖磁盘文件,请勿在有数据的磁盘上进行写测试

延时测试

bash
fio --ioengine=libaio --numjobs=1 --runtime=120 --direct=1 --time_based --group_reporting --ramp_time=0 --size=20G --bs=4k --iodepth=1 --filename=/dev/vdb --name=4k_randwrite --rw=randwrite

# [实际场景会测试顺序读、顺序写、顺序读写、随机读、随机写、随机读写等多类型]
  • --ioengine=libaio: 使用异步 I/O 引擎 libaio,即通过内核的异步 I/O 接口进行操作。
  • --numjobs=1: 启动 1 个作业(job)。在此配置中,只会有一个工作线程。
  • --runtime=120: 运行时间为 120 秒。这是测试的总持续时间。
  • --direct=1: 启用直接 I/O,绕过操作系统缓存,直接对设备进行 I/O 操作。
  • --time_based: 表示测试基于时间,而不是 I/O 总量。在指定时间内持续运行测试。
  • --group_reporting: 启用分组报告模式,汇总输出所有作业的结果。
  • --ramp_time=0: 没有指定热身时间,测试开始后立即进行计时。
  • --size=20G: 指定测试文件大小为 20 GB。在随机写测试中,将在这个范围内随机写数据。
  • --bs=4k: 块大小为 4 KB,每个 I/O 操作写入 4 KB 的数据。
  • --iodepth=1: 指定 I/O 深度为 1,意味着在任何给定时间,只有一个未完成的 I/O 操作。
  • --filename=/dev/vdb: 指定测试设备为 /dev/vdb,测试将在这个设备上执行。
  • --name=4k_randwrite: 为测试作业指定名称为 4k_randwrite
  • --rw=randwrite: 进行随机写操作。
bash
4k_randwrite: (g=0): rw=randwrite, bs=4K-4K/4K-4K/4K-4K, ioengine=libaio, iodepth=1
fio-3.19
Starting 1 process
4k_randwrite: Laying out IO file (1 file / 20971520MiB)

4k_randwrite: (groupid=0, jobs=1): err= 0: pid=12345: Tue Jul 31 15:34:56 2024
  write: IOPS=2500, BW=9.77MiB/s (10.2MB/s)(1170MiB/120010msec); 0 zone resets
   slat (usec): min=10, max=145, avg=15.50, stdev= 3.20 # 一般查看这三行里的 avg 数据
   clat (usec): min=50, max=750, avg=100.50, stdev= 15.20
    lat (usec): min=70, max=780, avg=116.00, stdev= 12.10
   clat percentiles (usec):
    |  1.00th=[  60],  5.00th=[  70], 10.00th=[  80], 20.00th=[  90],
    | 30.00th=[  95], 40.00th=[ 100], 50.00th=[ 105], 60.00th=[ 110],
    | 70.00th=[ 120], 80.00th=[ 130], 90.00th=[ 140], 95.00th=[ 150],
    | 99.00th=[ 170], 99.50th=[ 200], 99.90th=[ 250], 99.95th=[ 300],
    | 99.99th=[ 400]
  lat (nsec)   : 1000=0.00%
  lat (usec)   : 2=0.01%, 4=0.02%, 10=0.05%, 20=0.10%
  cpu          : usr=0.3%, sys=0.5%, ctx=292500, majf=0, minf=10
  IO depths    : 1=100.0%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0%
    submit: 0=0.0%, complete: 0=0.0%, issued: total=r=0/w=292500/d=0, short=0, dropped=0
    latency: target=0, window=0, percentile=100.00%, depth=1

Run status group 0 (all jobs):
  WRITE: bw=9.77MiB/s (10.2MB/s), 9.77MiB/s-9.77MiB/s (10.2MB/s-10.2MB/s), io=1170MiB (1227MB), run=120010-120010msec
  • 基本信息:
    • rw=randwrite: 测试是随机写操作。
    • bs=4K: 每个 I/O 操作写入 4 KB 数据。
    • iodepth=1: I/O 深度为 1,表示同一时间只有一个未完成的 I/O 请求。
  • 吞吐量和 IOPS:
    • write: IOPS=2500: 每秒 2500 次 I/O 操作。
    • BW=9.77MiB/s (10.2MB/s): 带宽为 9.77 MiB/s,相当于 10.2 MB/s。在整个测试期间,总共写入了 1170 MiB 数据。
  • 延迟统计:
    • slat (usec): Submission Latency, 提交延迟(从任务请求发出到任务开始执行的时间)平均为 15.50 微秒,最大 145 微秒。
    • clat (usec): Completion Latency, 完成延迟(从任务请求发出到任务完成的时间)平均为 100.50 微秒,最大 750 微秒。
    • lat (usec): 总延迟(从请求发出到完成的总时间)平均为 116.00 微秒,最大 780 微秒。
    • 延迟分布:
      • 百分位数提供了延迟的分布情况。例如,99% 的 I/O 操作在 170 微秒内完成,99.99% 的操作在 400 微秒内完成。
  • CPU 使用率:
    • usr=0.3%: 用户态 CPU 使用率为 0.3%。
    • sys=0.5%: 系统态 CPU 使用率为 0.5%。
  • I/O 深度分布:
    • 100% 的时间内 I/O 深度为 1,这与命令中指定的 iodepth=1 一致。
  • 这个测试结果展示了设备 /dev/vdb 在 4 KB 随机写操作中的性能表现。测试中,平均写带宽为 9.77 MiB/s,IOPS 为 2500。平均延迟为 116 微秒,最大延迟为 780 微秒。延迟分布表明,大多数 I/O 操作的延迟在 200 微秒以下,这对于了解设备在低 I/O 深度和高延迟敏感性的应用中的表现非常有帮助。

带宽测试

bash
fio --ioengine=libaio --numjobs=1 --runtime=120 --direct=1 --time_based --group_reporting --ramp_time=0 --size=20G --bs=128k --iodepth=128 --filename=/dev/vdb --name=128k_randwrite --rw=randwrite

# [实际场景会测试顺序读、顺序写、顺序读写、随机读、随机写、随机读写等多类型]
  • --bs=128k: 块大小设置为 128 KB。每次 I/O 操作会写入 128 KB 的数据。
  • --iodepth=128:指定 I/O 深度为 128,这表示在任意时刻,最多可以有 128 个未完成的 I/O 操作。这个参数对于测试高并发的 I/O 设备性能非常重要。
bash
128k_randwrite: (g=0): rw=randwrite, bs=(R) 128KiB-128KiB, (W) 128KiB-128KiB, ioengine=libaio, iodepth=128
fio-3.25
Starting 1 process
128k_randwrite: Laying out IO file (1 file / 0KiB)

128k_randwrite: (groupid=0, jobs=1): err= 0: pid=12345: Wed Jul 31 10:15:42 2024
  write: IOPS=2202, BW=275MiB/s (288MB/s)(32.2GiB/120000msec); 0 zone resets
    slat (nsec): min=400, max=217.2k, avg=714.80, stdev=733.23
    clat (usec): min=430, max=1474, avg=58.28, stdev=23.97
     lat (usec): min=432, max=1474, avg=59.00, stdev=23.99
    clat percentiles (usec):
     |  1.00th=[   44],  5.00th=[   47], 10.00th=[   49], 20.00th=[   51],
     | 30.00th=[   52], 40.00th=[   54], 50.00th=[   56], 60.00th=[   59],
     | 70.00th=[   62], 80.00th=[   66], 90.00th=[   75], 95.00th=[   84],
     | 99.00th=[  106], 99.50th=[  118], 99.90th=[  155], 99.95th=[  178],
     | 99.99th=[  279]
   bw (  KiB/s): min=278240, max=291072, per=100.00%, avg=281477.20, stdev=3243.90, samples=239 # 一般查看平均 BandWidth,若为混合读写,则有两个值
   iops        : min= 2173, max= 2274, avg=2198.90, stdev=25.34, samples=239
  lat (usec)   : 500=99.60%, 750=0.38%, 1000=0.01%
  cpu          : usr=3.51%, sys=5.34%, ctx=164058, majf=0, minf=42
  IO depths    : 1=0.0%, 2=0.1%, 4=0.2%, 8=0.5%, 16=1.2%, 32=2.3%, >=64=95.7%
     submit    : 0=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.1%, >=64=99.9%
     complete  : 0=0.0%, 4=0.0%, 8=0.0%, 16=0.1%, 32=0.5%, >=64=99.3%
     issued rwts: total=0,257795,0,0 short=0,0,0,0 dropped=0,0,0,0
     latency   : target=0, window=0, percentile=100.00%, depth=128
  • IOPS: 每秒的输入输出操作数。这里显示随机写的 IOPS 为 2202。
  • BW: 带宽,表示数据传输速率。此处为 275MiB/s。
  • lat (usec): 延迟,分为提交延迟 (slat),完成延迟 (clat),和总体延迟 (lat)。延迟分布显示在不同百分位的延迟情况。
  • cpu: CPU 使用率,包括用户态和内核态的比例。
  • IO depths: IO 深度的分布情况。

IOPS 测试

bash
fio --ioengine=libaio --numjobs=1 --runtime=120 --direct=1 --time_based --group_reporting --ramp_time=0 --size=20G --bs=4k --iodepth=128 --filename=/dev/vdb --name=4k_randwrite --rw=randwrite

# [实际场景会测试顺序读、顺序写、顺序读写、随机读、随机写、随机读写等多类型]
  • --bs=4k: 块大小为 4KB。
  • --iodepth=128: I/O 深度为 128。
bash
4k_randwrite: (g=0): rw=randwrite, bs=(R) 4KiB-4KiB, (W) 4KiB-4KiB, ioengine=libaio, iodepth=128
fio-3.25
Starting 1 process
4k_randwrite: Laying out IO file (1 file / 0KiB)

4k_randwrite: (groupid=0, jobs=1): err= 0: pid=23456: Wed Jul 31 11:15:42 2024
  write: IOPS=120000, BW=469MiB/s (492MB/s)(55.0GiB/120001msec); 0 zone resets
    slat (nsec): min=500, max=232.8k, avg=1500.40, stdev=1100.23
    clat (usec): min=150, max=1150, avg=300.32, stdev=50.97
     lat (usec): min=151, max=1152, avg=301.82, stdev=51.27
    clat percentiles (usec):
     |  1.00th=[  200],  5.00th=[  220], 10.00th=[  230], 20.00th=[  240],
     | 30.00th=[  250], 40.00th=[  270], 50.00th=[  290], 60.00th=[  310],
     | 70.00th=[  340], 80.00th=[  370], 90.00th=[  420], 95.00th=[  450],
     | 99.00th=[  500], 99.50th=[  550], 99.90th=[  600], 99.95th=[  650],
     | 99.99th=[  700]
   bw (  KiB/s): min=455360, max=480000, per=100.00%, avg=479436.00, stdev=3500.90, samples=240
   iops        : min=113840, max=120000, avg=119800.00, stdev=875.23, samples=240
  lat (usec)   : 250=15.00%, 500=82.50%, 750=2.50%
  cpu          : usr=10.00%, sys=20.00%, ctx=180500, majf=0, minf=40
  IO depths    : 1=0.0%, 2=0.1%, 4=0.2%, 8=0.3%, 16=0.4%, 32=1.0%, >=64=98.0%
     submit    : 0=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=100.0%
     complete  : 0=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=100.0%
     issued rwts: total=0,14112000,0,0 short=0,0,0,0 dropped=0,0,0,0
     latency   : target=0, window=0, percentile=100.00%, depth=128

fio 测试结果分析

  • 延时测试: --bs=4k --iodepth=1
    • 使用 4KB 的块大小(--bs=4k)和单一 I/O 深度(--iodepth=1)进行测试。
    • 这个配置通常用于测试系统在最低并发度下的响应时间,反映的是延迟性能。因为 IO 深度为 1,测试强调每个 I/O 操作的响应时间,使得延迟值通常是所有测试场景中最小的。
  • 带宽测试:--bs=128k--iodepth=128
    • 使用 128KB 的块大小(--bs=128k)和较大的 I/O 深度(--iodepth=128)。
    • 这种配置用于测量系统的最大数据吞吐能力,即带宽。因为较大的块大小和高 IO 深度能够充分利用设备的 I/O 能力,因而带宽测试场景下的带宽值通常是最大的。
  • IOPS 测试:--bs=4k --iodepth=128
    • 使用 4KB 的块大小(--bs=4k)和高 I/O 深度(--iodepth=128)。
    • 这个配置用于测量系统的 IOPS,即每秒能处理多少 I/O 操作。较小的块大小有助于提高 IOPS,因为较小的块处理速度快,而较高的 IO 深度增加了并发处理能力。因此,IOPS 测试场景下的 IOPS 值通常是最高的。

  • 延时测试关注单一操作的响应速度,因此延迟最小。
  • 带宽测试关注设备的数据传输能力,因此带宽最大。
  • IOPS 测试关注单位时间内的操作次数,因此 IOPS 最高。